-
Notifications
You must be signed in to change notification settings - Fork 195
task1 #160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
task1 #160
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
супер, отличная работа! можно было бы frozen_string_literal: true
ещё добавить
в целом вы вроде поправили всё основное, навскидку сложно что-то подсказывать (ведь я сам всегда говорю что гадать не надо, а надо профилировать)
вполне может получиться уложиться в 30 секунд во втором задании, когда подойдём к этой же проблеме с другой стороны
Я решил исправить эту проблему, оптимизировав эту программу. | ||
|
||
## Формирование метрики | ||
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: Время обработки текстового файла, содержащего 3,25 млн. строк. Бюджет данной метрики равен 30 секунд. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ды, но мы ещё формируем промежуточные метрики по дороге. Мы их используем чтобы понять насколько успешным было очередное изменение на очередной итерации. Это нормально, что мы используем новую метрику на разных шагах большого процесса.
Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. | ||
|
||
## Feedback-Loop | ||
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за ~48 секунд. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
- Определение главной точки роста с помощью профилирования | ||
- Нахождение в коде проблемного места, связанного с главной точкой роста | ||
- Внесение в код изменений, касающихся оптимизации только главной точки роста | ||
- Повторное профилирование и определение новой главной точки роста |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
по идее ещё замеры метрики до и после изменения, чтобы понимать какой эффект это изменение дало
- После сделанных замеров и анализа отчетов, получилось, что основной точкой роста является вызов метода select у массива. | ||
- В коде select вызывается только в одном месте для получения сессий пользователя внутри цикла по пользователям. | ||
- Я решил отказаться от этого метода, немного изменив хранение данных. В первом цикле по строкам файла, сессии я заполняю в новом хэше, ключами которого являются id пользователей. Таким образом, в цикле по заполнению массива объектов пользователей, получить сессии можно обратившись по ключу к хэшу. | ||
- После сделанных изменений проблема перестала быть основной точкой роста и программа стала выполняться в ~10 раз быстрей, т.е. выбранная метрика улучшилась в 10 раз. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
главное, что исправили сложность с O(N^2) на O(N)
- Точка роста осталась прежней, но уменьшилось количество вызовов добавления в массив | ||
|
||
### Вместо добавления элементов в массив, можно присваивать значения по индексу | ||
- Основная точка роста осталась операция Array#+. Я предположил, что на больших массивах быстрей будет операция присваивания по индексу, чем добавление нового элемента, т.к. в последнем случае интерпретатору нужно регулярно расширять занимаемую память массивом, на что тратится дополнительное время. Если массив изначально будет задан с определенным количеством элементов, то наполнение этого массива данными по индексу должно быть быстрей |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
идея подготовить массивы правильного размера классная! но Array#+ это скорее про то что используется форма a = a + [b], а надо a << b
- Проблема решилась, после профилирования на 100000 строк, основной точкой роста стала операция Array#each | ||
|
||
### Много циклов перебора элементов массива | ||
- Основной точкой роста стала операция Array#each, вызывающаяся 9 раз. Исходя из логики программы, более чем достаточно пройти по всем элементам всего 2 раза - сначала, для чтения данных из файла, затем, для заполнения итогового отчета. Но алгоритм реализован так, что вторым циклом заполняется массив объектов пользователей с нужными атрибутами, а потом, для 7 статистических показателей отчета производится итерирование по массиву объектов класса User. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Array#each, который вызывается 9 раз с блоками == не понятно на самом деле в чём проблема
Можно было бы например попробовать другие отчёты, либо порефакторить код (заменить анонимные блоки на именованые методы), чтобы понять более точно где проблема на самом деле
- Основной точкой роста стала операция Array#each, вызывающаяся 9 раз. Исходя из логики программы, более чем достаточно пройти по всем элементам всего 2 раза - сначала, для чтения данных из файла, затем, для заполнения итогового отчета. Но алгоритм реализован так, что вторым циклом заполняется массив объектов пользователей с нужными атрибутами, а потом, для 7 статистических показателей отчета производится итерирование по массиву объектов класса User. | ||
- Было принято решение отказаться вообще от массива объектов пользователей и 7 статистических показателей вычислять во втором цикле по массиву users. Это стало возможным, т.к. на предыдущих шагах оптимизации я сделал хэш, из которого в каждой итерации по массиву users, можно получить по ключу сессии пользователя. На этом шаге оптимизации я избавился от класса User и метода collect_stats_from_users. | ||
- Метрика улучшилась на ~30%, программа обработала файл со 100000 строками за ~2.2 сек. | ||
- Количество вызовов Array#each сократилось до 2, но при профилировании эта операция всё равно осталась по продолжительности на первом месте. Так произошло, потому что после проделанных изменений, все вычисления оказались внутри двух циклов. Поэтому, основную точку роста следует искать в дочерних вызовах. Такой получилась операция Array#map с 12% времени выполнения. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
да-да, дело-то не в each, а в блоках, которые выполняются
|
||
### Долгий парсинг даты | ||
- Профилирование на 100000 строк показало, что основной точкой роста является парсинг даты (<Class::Date>#parse). | ||
- Анализ исходных данных показал, что парсинг даты в целом не нужен, т.к. в исходных данных дата уже представлена в нужном формате iso8601. Сортировка строковых значений даст тот же эффект, что и сортировка объектов Date. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
report['usersStats'][user_key] ||= {} | ||
|
||
# Собираем количество сессий по пользователям | ||
report['usersStats'][user_key].merge!('sessionsCount' => user_sessions['sessions_count']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
выглядит, что можно было бы сгруппировать эти мержи в виде
report['usersStats'][user_key].merge!(
{
'sessionsCount' => user_sessions['sessions_count'],
'totalTime' => total_time,
...
}
No description provided.